export { deepMerge, deepMergeAll }
function deepMerge<T>(
  target: T,
  source: T,
  optionsArgument?: { clone: boolean },
): T {
  const array = Array.isArray(source)
  if (array) {
    return Array.isArray(target)
      ? defaultArrayMerge(target, source, optionsArgument)
      : cloneIfNecessary(source, optionsArgument)
  }
  else {
    return mergeObject<T>(target, source, optionsArgument)
  }
}

function deepMergeAll(
  array: any[],
  optionsArgument: { clone: boolean } | undefined,
) {
  if (!Array.isArray(array) || array.length < 2) {
    throw new Error(
      'first argument should be an array with at least two elements',
    )
  }
  return array.reduce((prev, next) => {
    return deepMerge(prev, next, optionsArgument)
  })
}

function isMergeableObject(val: any) {
  const nonNullObject = val && typeof val === 'object'

  return (
    nonNullObject
    && Object.prototype.toString.call(val) !== '[object RegExp]'
    && Object.prototype.toString.call(val) !== '[object Date]'
  )
}

function emptyTarget(val: any) {
  return Array.isArray(val) ? [] : {}
}

function cloneIfNecessary(
  value: any,
  optionsArgument: { clone: boolean } | undefined,
) {
  const clone = optionsArgument && optionsArgument.clone === true
  return clone && isMergeableObject(value)
    ? deepMerge(emptyTarget(value), value, optionsArgument)
    : value
}

function defaultArrayMerge(
  target: any[],
  source: any[],
  optionsArgument: { clone: boolean } | undefined,
) {
  const destination = target.slice()
  source.forEach((e: any, i: number) => {
    if (typeof destination[i] === 'undefined') {
      destination[i] = cloneIfNecessary(e, optionsArgument)
    }
    else if (isMergeableObject(e)) {
      // 直接赋值给对应的项
      // destination[i] = deepMerge(target[i], e, optionsArgument)
      // 追加到数组末尾
      destination.push(cloneIfNecessary(e, optionsArgument))
    }
    else if (!target.includes(e)) {
      destination.push(cloneIfNecessary(e, optionsArgument))
    }
  })
  return destination
}

function mergeObject<T>(
  target: T,
  source: T,
  optionsArgument: { clone: boolean } | undefined,
): T {
  const destination: Record<string, any> = {}
  if (isMergeableObject(target)) {
    Object.keys(target as Record<string, any>).forEach((key) => {
      destination[key] = cloneIfNecessary(
        (target as Record<string, any>)[key],
        optionsArgument,
      )
    })
  }
  Object.keys(source as Record<string, any>).forEach((key) => {
    if (
      !isMergeableObject((source as Record<string, any>)[key])
      || !(target as Record<string, any>)[key]
    ) {
      destination[key] = cloneIfNecessary(
        (source as Record<string, any>)[key],
        optionsArgument,
      )
    }
    else {
      destination[key] = deepMerge(
        (target as Record<string, any>)[key],
        (source as Record<string, any>)[key],
        optionsArgument,
      )
    }
  })
  return destination as T
}
